package com.bmit.clusterspring.service;

import algorithm.AGNES;
import algorithm.ClusterFactory;
import algorithm.Linkage;
import com.bmit.clusterspring.log.SaveClusterLogThread;
import com.bmit.clusterspring.model.BarVisualization;
import com.bmit.clusterspring.model.ClusterLog;
import com.bmit.clusterspring.model.ScatterVisualization;
import com.bmit.clusterspring.util.ThreadPoolUtils;
import dataset.CustomizedDataset;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import util.CsvUtils;

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.math.BigDecimal;
import java.util.*;

@Service
public class AGNESServiceImpl implements AGNESService {
    @Autowired
    private RedisTemplate<Object, Object> redisTemplate;

    @Autowired
    private LogService clusterLogService;

    @Value("${file.raw}")
    private String rawPath;

    @Value("${file.before}")
    private String beforePath;

    @Value("${file.result}")
    private String resultPath;

    @Override
    public boolean uploadCSV(String ipAddress, MultipartFile file) throws IOException {
        String nowNanoTime = String.valueOf(System.nanoTime());
        String token = nowNanoTime.substring(nowNanoTime.length() - 8);
        String oldRaw = (String) redisTemplate.opsForValue().get(ipAddress + "@raw");
        String fileName = token + "-raw-" + file.getOriginalFilename();
        BufferedOutputStream out = new BufferedOutputStream(
                new FileOutputStream(new File(rawPath + fileName)));
        out.write(file.getBytes());
        out.flush();
        out.close();
        redisTemplate.opsForValue().set(ipAddress + "@raw", fileName);
        System.out.println("raw:" + fileName);
        return true;
    }

    @Override
    public List<Map<String, String>> showCSV(String ipAddr) {
        String raw = (String) redisTemplate.opsForValue().get(ipAddr + "@raw");
        if (raw == null) return null;
        List<Map<String, String>> data = CsvUtils.csvToMap(rawPath + raw, ",");
        data = data.size() < 10 ? data : data.subList(0, 10);
        return data;
    }

    @Override
    public void setStrategy(String ipAddr, String strategy) {
        redisTemplate.opsForHash().put(ipAddr + "@param", "strategy", strategy);
    }

    @Override
    public void setParams(String ipAddr, String k, String linkage) {
        redisTemplate.opsForHash().put(ipAddr + "@param", "k", k);
        redisTemplate.opsForHash().put(ipAddr + "@param", "linkage", linkage);
    }

    @Override
    public void setColumns(String ipAddr, List<String> cols) {
        String fileName = (String) redisTemplate.opsForValue().get(ipAddr + "@raw");
        System.out.println("str:" + fileName);
        String[] split = fileName.split("-");
        String newFileName = split[0] + "-before-" + split[2];
        CsvUtils.subCsv(rawPath + fileName, beforePath + newFileName, ",", cols);
        deleteFile(rawPath + fileName);
        redisTemplate.opsForValue().set(ipAddr + "@before", newFileName);
        redisTemplate.opsForValue().set(ipAddr + "@size", String.valueOf(cols.size()));
        System.out.println("str:" + newFileName);
    }

    @Override
    public void runCluster(String ipAddr) {
        String beforeFileName = (String) redisTemplate.opsForValue().get(ipAddr + "@before");
        System.out.println("run:" + beforeFileName);
        List<List<String>> dataString = CsvUtils.csvToList(beforePath + beforeFileName, ",");
        deleteFile(beforePath + beforeFileName);
        Integer strategy = Integer.parseInt((String) redisTemplate.opsForHash().get(ipAddr + "@param", "strategy"));
        Integer k = Integer.parseInt((String) redisTemplate.opsForHash().get(ipAddr + "@param", "k"));
        Integer linkageIndex = Integer.parseInt((String) redisTemplate.opsForHash().get(ipAddr + "@param", "linkage"));
        Linkage linkage = linkageIndex == 0 ? Linkage.AVERAGE_LINKAGE : linkageIndex == 1 ? Linkage.COMPLETE_LINKAGE : Linkage.SINGLE_LINKAGE;
        CustomizedDataset ds = new CustomizedDataset(dataString, strategy);
        ClusterFactory clusteringFactory = new ClusterFactory();
        AGNES agnes = (AGNES) clusteringFactory.create(ClusterFactory.AGNES);
        agnes.setK(k);
        agnes.setLinkage(linkage);
        String[] split = beforeFileName.split("-");
        long time = agnes.fit(ds);
        String resultName = split[0] + "-AGNES-result-" + split[2];
        redisTemplate.opsForValue().set(ipAddr + "@result", resultName);
        redisTemplate.opsForValue().set(ipAddr + "@time", String.valueOf(time));
        System.out.println("run:" + resultName);
        CsvUtils.writeCsv(resultPath + resultName, ds.dataset, agnes.result.label, ",");
        ClusterLog clusterLog = new ClusterLog();
        clusterLog.setIpAddress(ipAddr);
        clusterLog.setAlgorithm("AGNES");
        clusterLog.setClusterNum(k);
        clusterLog.setRunTime(Integer.parseInt(String.valueOf(time)));
        clusterLog.setDataShape0(ds.shape[0]);
        clusterLog.setDataShape1(ds.shape[1]);
        clusterLog.setGmtCreate(new Date());
        ThreadPoolUtils.getPool().execute(new SaveClusterLogThread(clusterLogService, clusterLog));
    }

    @Override
    public String resultName(String ipAddr) {
        return (String) redisTemplate.opsForValue().get(ipAddr + "@result");
    }

    @Override
    public Map<String, ScatterVisualization> scatter(String ipAddr) {
        String resultNameFileName = (String) redisTemplate.opsForValue().get(ipAddr + "@result");
        List<Map<String, String>> resultMaps = CsvUtils.csvToMap(resultPath + resultNameFileName, ",");
        Map<String, ScatterVisualization> res = new HashMap<>();
        for (Map<String, String> map : resultMaps) {
            ScatterVisualization sv = res.getOrDefault(map.get("c2"), new ScatterVisualization("cluster-" + map.get("c2"), new ArrayList<>()));
            List<Double> point = new ArrayList<>();
            point.add(Double.parseDouble(map.get("c0")));
            point.add(Double.parseDouble(map.get("c1")));
            sv.getPointList().add(point);
            res.put(map.get("c2"), sv);
        }
        return res;
    }

    @Override
    public Map<String, BarVisualization> bar(String ipAddr) {
        String resultNameFileName = (String) redisTemplate.opsForValue().get(ipAddr + "@result");
        Integer size = Integer.parseInt((String) redisTemplate.opsForValue().get(ipAddr + "@size"));
        List<List<String>> fileList = CsvUtils.csvToList(resultPath + resultNameFileName, ",");
        Map<Integer, Integer> clusterNum = new LinkedHashMap<>();
        Map<Integer, List<Double>> clusterSum = new LinkedHashMap<>();
        List<Integer> labels = new ArrayList<>();
        Set<Integer> set = new HashSet<>();
        for (List<String> line : fileList) {
            int label = Integer.parseInt(line.get(size));
            if (!set.contains(label)) {
                labels.add(label);
                set.add(label);
            }
        }
        Collections.sort(labels);
        for (Integer label : labels) {
            clusterNum.put(label, 0);
            ArrayList<Double> list = new ArrayList<>();
            for (Integer i = 0; i < size; i++) {
                list.add(0.);
            }
            clusterSum.put(label, list);
        }
        for (List<String> line : fileList) {
            int label = Integer.parseInt(line.get(size));
            clusterNum.put(label, clusterNum.get(label) + 1);
            List<Double> list = clusterSum.get(label);
            for (int i = 0; i < list.size(); i++) {
                Double data = Double.parseDouble(line.get(i));
                list.set(i, list.get(i) + data);
            }
        }
        for (Integer label : labels) {
            Integer count = clusterNum.get(label);
            List<Double> list = clusterSum.get(label);
            for (int i = 0; i < list.size(); i++)
                list.set(i, list.get(i) / count);
        }
        Map<String, BarVisualization> res = new LinkedHashMap<>();
        for (Integer i = 0; i < size; i++) {
            String columnName = "column-" + i;
            List<Double> list = new ArrayList<>();
            for (Integer label : labels) {
                BigDecimal f = new BigDecimal(clusterSum.get(label).get(i));
                list.add(f.setScale(3, BigDecimal.ROUND_HALF_UP).doubleValue());
            }
            BarVisualization bv = new BarVisualization(columnName, list);
            res.put(columnName, bv);
        }
        return res;
    }

    @Override
    public boolean checkSize(String ipAddr) {
        String resultSize = (String) redisTemplate.opsForValue().get(ipAddr + "@size");
        return resultSize.equals("2");
    }

    @Override
    public String getTime(String ipAddr) {
        return (String) redisTemplate.opsForValue().get(ipAddr + "@time");
    }

    public boolean deleteFile(String path) {
        return new File(path).delete();
    }
}
